#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include "server.h"
int isfirst = 1;
char cwd[MAX_STRING_SIZE];
int root;

int
rls(PACKAGE *package, struct sockaddr_in sock) {
    // definimos el buffer donde cargaremos el contenido del directorio
    // y el arreglo donde guardamos el nombre del directorio de trabajo
    static char buffer[MAX_STRING_SIZE];

    // 'vaciamos' el buffer para evitar devolver datos sucios
    memset(buffer, '\0', MAX_STRING_SIZE);

    // si es la primera vez que se ejecuta la función,
    // seteamos el directorio de trabajo actual
    if (isfirst == 1) {
        isfirst = 0;
        getcwd(cwd, MAX_STRING_SIZE);
    }

    // estructuras mediante las cuales leeremos el contenido del directorio
    DIR *dp1;
    struct dirent *dirp;

    // abrimos el directorio
    dp1 = opendir(cwd);

    // si no se puede abrir el directorio...
    if (!dp1) {
        strcpy(buffer, "No se pudo leer el directorio.\n");
    } else {
        // leemos contenido del directorio y agregamos los nombres
        // de archivos o directorios al buffer, descartando el directorio
        // actual y el 'padre'
        while ((dirp = readdir(dp1)) != NULL) {
            if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
                continue;
            if (strlen(buffer) != 0)
                strcpy(&buffer[strlen(buffer)], "\n");
            strcpy(&buffer[strlen(buffer)], dirp->d_name);
            // si es un directorio, agrego [DIR] a la salida
            if (dirp->d_type == 0x004)
                strcpy(&buffer[strlen(buffer)], " - [DIR]");

        }
        closedir(dp1);
        // si el buffer quedó vacio, no hay archivos ni directorios
        // que mostrar
        if (strlen(buffer) == 0) {
            strcpy(buffer, "Directorio vacio.\n");
        }

        segmentar(buffer, package, sock);
    }
}

int
rcd(PACKAGE * package, struct sockaddr_in sock) {
    // si es la primera vez que se ejecuta la función,
    // seteamos el directorio de trabajo actual
    if (isfirst == 1) {
        isfirst = 0;
        getcwd(cwd, MAX_STRING_SIZE);
    }

    int result = -1;

    if (package->data[0] == '/' && strcmp(package->data, "/..") != 0) {
        cwd[root] = '\0';
        result = chdir(strcat(cwd, package->data));
    } else
        if (strlen(cwd) != root || strcmp(package->data, "..") != 0)
        result = chdir(package->data);


    static char res[MAX_STRING_SIZE];
    if (result == 0) {
        strcpy(res, "Se cambió correctamente el directorio.\nEl directorio actual es: ");
        getcwd(cwd, MAX_STRING_SIZE);
        strcat(res, &cwd[root]);
        if (strlen(cwd) == root)
            strcat(res, "/");
    } else {
        strcpy(res, "No se pudo cambiar de directorio.\nUd. intentó cambiar a: ");
        strcat(res, package->data);
    }
    package->op = ACK;
    enviar(package, sock, 0);
    return segmentar(res, package, sock);
}

int
get(PACKAGE * package, struct sockaddr_in sock) {
    FILE *file;
    file = fopen(package->data, "r");
    if (file == NULL) {
        package->op = ERROR;
    } else {
        package->op = OK;
        int i = package->block_count;
        i *= DATA_LENGTH;
        fseek(file, i, SEEK_SET);
        package->offset = fread(package->data, sizeof (char), DATA_LENGTH, file);
        fclose(file);
    }
    return enviar(package, sock, 0);
}

int
put(PACKAGE * package, struct sockaddr_in sock) {
    int i;
    // puntero al archivo que se crea del lado del cliente
    FILE *file;
    // abro para crear o sobreescribir el archivo...
    if (package->block_count == 0)
        file = fopen(package->data, "w");
        // o para agregarle bloques subsiguientes
    else
        file = fopen(package->data, "a");

    if (file != NULL) {
        package->op = OK;
        enviar(package, sock, 0);
        if((i = recibir(package, sock, MSG_DONTWAIT)) < 0) {
            printf("No se recibió el paquete esperado del cliente.");
            return i;
        }
        // escribo el bloque
        i = fwrite(package->data, sizeof (char), package->offset, file);
        fclose(file);
        package->op = ACK;
        enviar(package, sock, 0);
    } else {
        package->op = ERROR;
        enviar(package, sock, 0);
    }
    return i;
}

int read_conf(void) {
    char *buffer, *prop, *value;
    FILE *conf_file = NULL;

    // abrimos el archivo de configuración
    conf_file = fopen(CONF_FILE, "r");
    if (conf_file == NULL) {
        printf("No se puede leer el archivo de configuración.\n");
        return ERROR;
    }

    getcwd(cwd, MAX_STRING_SIZE);

    buffer = (char *) malloc(MAX_STRING_SIZE);
    prop = (char *) malloc(MAX_STRING_SIZE);
    value = (char *) malloc(MAX_STRING_SIZE);

    printf("Configuración del servidor\n");
    printf("-------------------------------------------\n");
    // leemos el archivo linea a linea
    while (get_line(buffer, conf_file) != EOF) {

        // descarto los comentarios
        if (buffer[0] == COMMENT) {
            continue;
        }
        // 'tokenizo' la linea, por '='
        strcpy(prop, strtok(buffer, "="));
        strcpy(value, strtok(NULL, "="));

        //si es la especificacion del directorio raiz de trabajo
        if (strcasecmp(prop, ROOT) == 0) {
            int i = 0;
            if ((i = chdir(value)) == 0) {
                isfirst = 0;
                getcwd(cwd, MAX_STRING_SIZE);
                root = strlen(cwd);
                printf("Directorio de trabajo: %s\n", cwd); //Muestra la carpeta pública
            }
        } else
            //si es la especificacion del puerto de escucha
            if (strcasecmp(prop, PORT) == 0) {
            port = atoi(value);
            printf("Puerto de escucha: %i\n", port); //Muestra el puerto de escucha
        }
    }
    printf("-------------------------------------------\n");

    free(buffer);
    free(prop);
    free(value);

    fclose(conf_file);

    return OK;
}

int get_line(char Dest[], FILE * f) {
    int t = 0;
    char c;

    do {
        c = fgetc(f);
        if (c != '\n') Dest[t++] = c;
    } while (c != '\n' && c != EOF);
    Dest[t++] = '\0';
    if (t == 1)
        return get_line(Dest, f);
    else
        return c == EOF ? -1 : t - 1;
}

int enviar(PACKAGE *package, struct sockaddr_in sock, int flag) {
    return sendto(package->to, package, sizeof (PACKAGE), flag, (struct sockaddr *) &sock, sizeof (sock));
}

int recibir(PACKAGE *package, struct sockaddr_in sock, int flag) {
    int sock_size = sizeof (sock), n, i;
    // recibo el paquete
        errno = EAGAIN;
        n = -1;
        for (i = 0; errno == EAGAIN && i <= TIMEOUT && n < 0; i++) {
            n = recvfrom(package->to, package, sizeof(PACKAGE), flag, (struct sockaddr *) &sock, &sock_size);
            usleep(1000);
        }
        if (n < 0 && errno != EAGAIN) {
            printf("Error nº %i recibiendo paquete.\n", errno);
        } else
        if (n < 0 && errno == EAGAIN) {
            printf("Timeout recibiendo paquete.\n");
        }

    return n;
}

int segmentar(char * data, PACKAGE * package, struct sockaddr_in sock) {
    char data_block[DATA_LENGTH];
    memset(data_block, '\0', DATA_LENGTH);
    int i, j, count = 0;
    package->block_count = 0;
    for (i = 0, j = 0; i < strlen(data); i++) {
        data_block[j] = data[i];
        if ((j + 2) == DATA_LENGTH || (i + 1) == strlen(data)) {
            data_block[j + 1] = '\0';
            strcpy(package->data, data_block);
            package->offset = strlen(data_block) + 1;
            package->block_count++;
            count += enviar(package, sock, 0);
            if(recibir(package, sock, MSG_DONTWAIT) < 0 ) {
                printf("Ocurrió un error recibiendo el ACK.\n");
                return count;
            } else
            if(package->op != ACK) {
                printf("El cliente envió '%i' cuando se esperaba ACK.\n", package->op);
                return count;
            }
            memset(data_block, '\0', DATA_LENGTH);
            j = 0;
        } else
            j++;
    }
    return count;
}